// Also code to look at the X visual and figure out the best way to turn
// a color into a pixel value.

// SGI compiler seems to have problems with unsigned char arguments
// being used to index arrays.  So I always copy them to an integer
// before use.

#if __FLTK_LINUX__

#  include "src/Fl_XColor.H"
#  include "Fl.H"
#  include "x.H"
#  include "fl_draw.H"

////////////////////////////////////////////////////////////////
// figure_out_visual() calculates masks & shifts for generating
// pixels in true-color visuals:

uchar fl_redmask;	/**< color mask used in current color map handling */
uchar fl_greenmask;	/**< color mask used in current color map handling */
uchar fl_bluemask;	/**< color mask used in current color map handling */

int fl_redshift;	/**< color shift used in current color map handling */
int fl_greenshift;	/**< color shift used in current color map handling */
int fl_blueshift;	/**< color shift used in current color map handling */
int fl_extrashift;	/**< color shift used in current color map handling */

static uchar beenhere;

static void figure_out_visual()
{
	beenhere = 1;
	if (!fl_visual->red_mask || !fl_visual->green_mask || !fl_visual->blue_mask) {
#  if USE_COLORMAP
		fl_redmask = 0;
		return;
#  else
Fl::fatal("Requires true color visual");
#  endif
	}

	// get the bit masks into a more useful form:
	int i,j,m;

	for (i = 0, m = 1; m; i++, m<<=1) if (fl_visual->red_mask & m) break;
	for (j = i; m; j++, m<<=1) if (!(fl_visual->red_mask & m)) break;
	fl_redshift = j-8;
	fl_redmask = (j-i >= 8) ? 0xFF : 0xFF-(255>>(j-i));

	for (i = 0, m = 1; m; i++, m<<=1) if (fl_visual->green_mask & m) break;
	for (j = i; m; j++, m<<=1) if (!(fl_visual->green_mask & m)) break;
	fl_greenshift = j-8;
	fl_greenmask = (j-i >= 8) ? 0xFF : 0xFF-(255>>(j-i));

	for (i = 0, m = 1; m; i++, m<<=1) if (fl_visual->blue_mask & m) break;
	for (j = i; m; j++, m<<=1) if (!(fl_visual->blue_mask & m)) break;
	fl_blueshift = j-8;
	fl_bluemask = (j-i >= 8) ? 0xFF : 0xFF-(255>>(j-i));

	i = fl_redshift;
	if (fl_greenshift < i) i = fl_greenshift;
	if (fl_blueshift < i) i = fl_blueshift;
	if (i < 0) {
		fl_extrashift = -i;
		fl_redshift -= i;
		fl_greenshift -= i;
		fl_blueshift -= i;
	} else
		fl_extrashift = 0;

}

static unsigned fl_cmap[256] = {
#include "src/fl_cmap.h" // this is a file produced by "cmap.cxx":
};

#  if HAVE_OVERLAY
/** HAVE_OVERLAY determines whether fl_xmap is one or two planes */
Fl_XColor fl_xmap[2][256];
/** HAVE_OVERLAY determines whether fl_overlay is variable or defined as 0 */
uchar fl_overlay;
Colormap fl_overlay_colormap;
XVisualInfo* fl_overlay_visual;
ulong fl_transparent_pixel;
#  else
/** HAVE_OVERLAY determines whether fl_xmap is one or two planes */
Fl_XColor fl_xmap[1][256];
/** HAVE_OVERLAY determines whether fl_overlay is variable or defined as 0 */
#    define fl_overlay 0
#  endif

void Fl_Xlib_Graphics_Driver::color(Fl_Color i)
{
	if (i & 0xffffff00) {
		unsigned rgb = (unsigned)i;
		fl_color((uchar)(rgb >> 24), (uchar)(rgb >> 16), (uchar)(rgb >> 8));
	} else {
		Fl_Graphics_Driver::color(i);
		if(!fl_gc) return; // don't get a default gc if current window is not yet created/valid
		XSetForeground(fl_display, fl_gc, fl_xpixel(i));
	}
}

void Fl_Xlib_Graphics_Driver::color(uchar r,uchar g,uchar b)
{
	Fl_Graphics_Driver::color( fl_rgb_color(r, g, b) );
	if(!fl_gc) return; // don't get a default gc if current window is not yet created/valid
	XSetForeground(fl_display, fl_gc, fl_xpixel(r,g,b));
}

/** \addtogroup  fl_attributes
    @{ */
////////////////////////////////////////////////////////////////
// Get an rgb color.  This is easy for a truecolor visual.  For
// colormapped it picks the closest color out of the cube in the
// fltk colormap.  However if this color cube entry has been
// requested before, you will get the earlier requested color, and
// even this may be approximated if the X colormap was full.

/**
  Returns the X pixel number used to draw the given rgb color.
  This is the X pixel that fl_color() would use.
  \param[in] r,g,b color components
  \return X pixel number
*/
ulong fl_xpixel(uchar r,uchar g,uchar b)
{
	if (!beenhere) figure_out_visual();
#  if USE_COLORMAP
	if (!fl_redmask) {
		// find closest entry in the colormap:
		Fl_Color i =
		        fl_color_cube(r*FL_NUM_RED/256,g*FL_NUM_GREEN/256,b*FL_NUM_BLUE/256);
		Fl_XColor &xmap = fl_xmap[fl_overlay][i];
		if (xmap.mapped) return xmap.pixel;
		// if not black or white, change the entry to be an exact match:
		if (i != FL_COLOR_CUBE && i != 0xFF)
			fl_cmap[i] = (r<<24)|(g<<16)|(b<<8);
		return fl_xpixel(i); // allocate an X color
	}
#  endif
	return
	        (((r&fl_redmask) << fl_redshift)+
	         ((g&fl_greenmask)<<fl_greenshift)+
	         ((b&fl_bluemask)<< fl_blueshift)
	        ) >> fl_extrashift;
}

////////////////////////////////////////////////////////////////
// Get a color out of the fltk colormap.  Again for truecolor
// visuals this is easy.  For colormap this actually tries to allocate
// an X color, and does a least-squares match to find the closest
// color if X cannot allocate that color.

// calculate what color is actually on the screen for a mask:
static inline uchar realcolor(uchar color, uchar mask)
{
#  if 0
	// accurate version if the display has linear gamma, but fl_draw_image
	// works better with the simpler version on most screens...
	uchar m = mask;
	uchar result = color&m;
	for (;;) {
		while (m&mask) {
			m>>=1;
			color>>=1;
		}
		if (!m) break;
		mask = m;
		result |= color&m;
	}
	return result;
#  else
return (color&mask) | ( (~mask)&(mask>>1) );
#  endif
}

/**
  Returns the X pixel number used to draw the given FLTK color index.
  This is the X pixel that fl_color() would use.
  \param[in] i color index
  \return X pixel number
*/
ulong fl_xpixel(Fl_Color i)
{
	if (i & 0xffffff00) {
		return fl_xpixel((i >> 24) & 255, (i >> 16) & 255, (i >> 8) & 255);
	}

	Fl_XColor &xmap = fl_xmap[fl_overlay][i];
	if (xmap.mapped) return xmap.pixel;

	if (!beenhere) figure_out_visual();

	uchar r,g,b;
	{
		unsigned c = fl_cmap[i];
		r=uchar(c>>24);
		g=uchar(c>>16);
		b=uchar(c>>8);
	}

#  if USE_COLORMAP
	Colormap colormap = fl_colormap;
#    if HAVE_OVERLAY
	if (fl_overlay) colormap = fl_overlay_colormap;
	else
#    endif
		if (fl_redmask) {
#  endif
			// return color for a truecolor visual:
			xmap.mapped = 2; // 2 prevents XFreeColor from being called
			xmap.r = realcolor(r, fl_redmask);
			xmap.g = realcolor(g, fl_greenmask);
			xmap.b = realcolor(b, fl_bluemask);
			return xmap.pixel =
			               (((r&fl_redmask) << fl_redshift)+
			                ((g&fl_greenmask)<<fl_greenshift)+
			                ((b&fl_bluemask)<< fl_blueshift)
			               ) >> fl_extrashift;
#  if USE_COLORMAP
		}
#    if HAVE_OVERLAY
	static XColor* ac[2];
	XColor*& allcolors = ac[fl_overlay];
	static int nc[2];
	int& numcolors = nc[fl_overlay];
#    else
static XColor *allcolors;
static int numcolors;
#    endif

	// I don't try to allocate colors with XAllocColor once it fails
	// with any color.  It is possible that it will work, since a color
	// may have been freed, but some servers are extremely slow and this
	// avoids one round trip:
	if (!numcolors) { // don't try after a failure
		XColor xcol;
		xcol.red = r<<8;
		xcol.green = g<<8;
		xcol.blue = b<<8;
		if (XAllocColor(fl_display, colormap, &xcol)) {
			xmap.mapped = 1;
			xmap.r = xcol.red>>8;
			xmap.g = xcol.green>>8;
			xmap.b = xcol.blue>>8;
			return xmap.pixel = xcol.pixel;
		}

		// I only read the colormap once.  Again this is due to the slowness
		// of round-trips to the X server, even though other programs may alter
		// the colormap after this and make decisions here wrong.
#    if HAVE_OVERLAY
		if (fl_overlay) numcolors = fl_overlay_visual->colormap_size;
		else
#    endif
			numcolors = fl_visual->colormap_size;
		if (!allcolors) allcolors = new XColor[numcolors];
		for (int p = numcolors; p--;) allcolors[p].pixel = p;
		XQueryColors(fl_display, colormap, allcolors, numcolors);
	}

	// find least-squares match:
	int mindist = 0x7FFFFFFF;
	unsigned int bestmatch = 0;
	for (unsigned int n = numcolors; n--;) {
#    if HAVE_OVERLAY
		if (fl_overlay && n == fl_transparent_pixel) continue;
#    endif
		XColor &a = allcolors[n];
		int d, t;
		t = int(r)-int(a.red>>8);
		d = t*t;
		t = int(g)-int(a.green>>8);
		d += t*t;
		t = int(b)-int(a.blue>>8);
		d += t*t;
		if (d <= mindist) {
			bestmatch = n;
			mindist = d;
		}
	}
	XColor &p = allcolors[bestmatch];

	// It appears to "work" to not call this XAllocColor, which will
	// avoid another round-trip to the server.  But then X does not
	// know that this program "owns" this value, and can (and will)
	// change it when the program that did allocate it exits:
	if (XAllocColor(fl_display, colormap, &p)) {
		xmap.mapped = 1;
		xmap.pixel = p.pixel;
	} else {
		// However, if that XAllocColor fails, I have to give up and
		// assume the pixel is ok for the duration of the program.  This
		// is due to bugs (?) in the Solaris X and some X terminals
		// where XAllocColor *always* fails when the colormap is full,
		// even if we ask for a color already in it...
		xmap.mapped = 2; // 2 prevents XFreeColor from being called
		xmap.pixel = bestmatch;
	}
	xmap.r = p.red>>8;
	xmap.g = p.green>>8;
	xmap.b = p.blue>>8;
	return xmap.pixel;
#  endif
}

/**
  Free color \p i if used, and clear mapping table entry.
  \param[in] i color index
  \param[in] overlay 0 for normal, 1 for overlay color
*/
void Fl::free_color(Fl_Color i, int overlay)
{
#  if HAVE_OVERLAY
#  else
if (overlay) return;
#  endif
	if (fl_xmap[overlay][i].mapped) {
#  if USE_COLORMAP
#    if HAVE_OVERLAY
		Colormap colormap = overlay ? fl_overlay_colormap : fl_colormap;
#    else
Colormap colormap = fl_colormap;
#    endif
		if (fl_xmap[overlay][i].mapped == 1)
			XFreeColors(fl_display, colormap, &(fl_xmap[overlay][i].pixel), 1, 0);
#  endif
		fl_xmap[overlay][i].mapped = 0;
	}
}

/**
  Set color mapping table entry \p i to color \p c
  \param[in] i color index
  \param[in] c color
*/
void Fl::set_color(Fl_Color i, unsigned c)
{
	if (fl_cmap[i] != c) {
		free_color(i,0);
#  if HAVE_OVERLAY
		free_color(i,1);
#  endif
		fl_cmap[i] = c;
	}
}

#endif
